package alipay

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"hash"
	"io/ioutil"
	"regexp"
	"sort"
	"strings"
	"time"

	"golang.org/x/text/encoding"
	"golang.org/x/text/encoding/simplifiedchinese"
	"golang.org/x/text/transform"
)

func (a *AlipayClient) getTimestamp() string {
	return time.Now().Format("2006-01-02 15:04:05")
}

func (a *AlipayClient) getSignType() string {
	if a.signType != "" {
		return a.signType
	}
	key, err := a.getPrivateKey()
	if err != nil {
		return "RSA2"
	}
	if key.N.BitLen() > 1024 {
		a.signType = "RSA2"
	} else {
		a.signType = "RSA"
	}
	return a.signType
}

func (a *AlipayClient) getPrivateKey() (*rsa.PrivateKey, error) {
	if a.privateKey != nil {
		return a.privateKey, nil
	}
	if a.PrivateKey == nil {
		return nil, nil
	}
	// Parse PEM block
	var block *pem.Block
	if block, _ = pem.Decode(a.PrivateKey); block == nil {
		return nil, errors.New("私钥格式错误")
	}

	var key interface{}
	var err error
	if key, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
		if key, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
			return nil, errors.New("私钥格式错误")
		}
	}
	a.privateKey, _ = key.(*rsa.PrivateKey)
	return a.privateKey, nil
}

func (a *AlipayClient) getPublicKey() (*rsa.PublicKey, error) {
	if a.publicKey != nil {
		return a.publicKey, nil
	}
	if a.AliPayPublicKey == nil {
		return nil, nil
	}
	// Parse PEM block
	var block *pem.Block
	if block, _ = pem.Decode(a.AliPayPublicKey); block == nil {
		return nil, errors.New("私钥格式错误")
	}

	var key interface{}
	var err error
	if key, err = x509.ParsePKCS1PublicKey(block.Bytes); err != nil {
		if key, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
			return nil, errors.New("公钥格式错误")
		}
	}
	a.publicKey, _ = key.(*rsa.PublicKey)
	return a.publicKey, nil
}

func (a *AlipayClient) _getHashSignType() (hash.Hash, crypto.Hash) {
	var h hash.Hash
	var hType crypto.Hash
	if a.signType == "RSA2" {
		h = sha256.New()
		hType = crypto.SHA256
	} else {
		h = sha1.New()
		hType = crypto.SHA1
	}
	return h, hType
}

func (a *AlipayClient) _sign(sortStr string) (string, error) {
	key, err := a.getPrivateKey()
	if err != nil {
		return "", err
	}
	h, hType := a._getHashSignType()
	_, _ = h.Write([]byte(sortStr))
	hashed := h.Sum(nil)
	bSign, err := rsa.SignPKCS1v15(rand.Reader, key, hType, hashed)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(bSign), nil
}

func (a *AlipayClient) fieldToString(v interface{}) string {
	t := ""
	switch v.(type) {
	case string:
		t = v.(string)
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
		t = fmt.Sprintf("%v", v)
	case bool:
		if v.(bool) {
			t = "true"
		} else {
			t = "false"
		}
	case float32, float64:
		t = fmt.Sprintf("%v", v)
	case []int, []int8, []int16, []int32, []int64, []uint, []uint8, []uint16, []uint32, []uint64:
		b, _ := json.Marshal(v)
		t = string(b)
	case []string, []float32, []float64, []bool:
		b, _ := json.Marshal(v)
		t = string(b)
	default:
		t = ""
	}
	return t
}

func (a *AlipayClient) _genSortStr(param map[string]interface{}) string {
	keys := make([]string, 0, len(param))
	for k := range param {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	var res []string
	for _, k := range keys {
		if strings.ToLower(k) == "sign" {
			continue
		}
		t := a.fieldToString(param[k])
		if t != "" {
			res = append(res, fmt.Sprintf("%s=%s", k, t))
		}
	}
	return strings.Join(res, "&")
}

// 请求签名
func (a *AlipayClient) Sign(param map[string]interface{}) (string, error) {
	// 生成字典序字符串
	sortStr := a._genSortStr(param)
	// 生成签名
	return a._sign(sortStr)
}

func (a *AlipayClient) _verify(rawStr []byte, sign string) error {
	key, err := a.getPublicKey()
	if err != nil {
		return err
	}
	h, hType := a._getHashSignType()
	_, _ = h.Write(rawStr)
	hashed := h.Sum(nil)
	bSign, err := base64.StdEncoding.DecodeString(sign)
	if err != nil {
		return err
	}
	return rsa.VerifyPKCS1v15(key, hType, hashed, bSign)
}

// 请求返回签名
func (a *AlipayClient) ResponseVerify(resp []byte) error {
	reg := regexp.MustCompile(`{"[a-z_]*_response":({".*"}),"sign":"(.*)"}`)
	rr := reg.FindSubmatch(resp)
	if len(rr) != 3 {
		return nil
	}
	sign := string(rr[2])
	if sign == "" {
		return nil
	}
	return a._verify(rr[1], sign)
}

// 解码
func (a *AlipayClient) decodeResp(charset string, s []byte) ([]byte, error) {
	i := bytes.NewReader(s)
	var decoder *encoding.Decoder
	charset = strings.ToLower(charset)
	if strings.Contains(charset, "gbk") {
		decoder = simplifiedchinese.GBK.NewDecoder()
	} else if strings.Contains(charset, "gb2312") {
		decoder = simplifiedchinese.HZGB2312.NewDecoder()
	} else {
		return s, nil
	}
	o := transform.NewReader(i, decoder)
	d, err := ioutil.ReadAll(o)
	if err != nil {
		return nil, err
	}
	return d, err
}
